Chapter 4
Painting, Device Contexts, Bitmaps, and Fonts

by Rob McGregor

In This Chapter

  Device Contexts 148
  The Graphics Device Interface 149
  MFC Device Context Classes 150
  Windows Graphic Objects 158
  GDI Coordinate Systems 165
  Vector Graphics 167
  Fonts and Text 184
  Sample Program: Vector Graphics and Text Methods (VECTEXT1.EXE) 194
  Raster Graphics 196
  Bitmap Resources 203
  Sample Program: Exploring Bitmap Resources (BITMAP1) 206

Graphics are extremely important in today’s GUI OS world, and Windows is certainly no exception. Because Windows provides a graphical user interface (GUI) to the underlying operating system (OS) and hardware, graphics are the mainstay of Windows programs. This chapter explains how MFC packages and exposes device contexts and graphics objects. This chapter also introduces the basic concepts of creating, drawing, and using graphics in Windows.


Note:  

Four sample programs (VECTEXT1, RASTER1, BEZIER1, and BITMAP1) included on this book’s accompanying CD-ROM demonstrate all of the techniques described in this chapter, and more, with fully commented source code.


Everything—even text—paints to the screen as graphics under Windows. The logical representation of a physical device’s drawing surface is packaged into a complex data structure called a device context (DC). Windows provides several special datatypes and structures to represent and describe each of the fundamental Windows graphic objects (including pens, brushes, fonts, and bitmaps).

Device Contexts

When a Windows program (including Windows itself) draws text and graphics to a display or some other device (such as a printer), it usually doesn’t draw directly to the hardware as DOS programs do. In fact, applications that write directly to hardware are considered taboo in the world of Windows. Applications use a device context (DC) to represent a logical version of the physical device, be it a monitor, a printer, a plotter, or some other physical device. A DC contains information about the pen, brush, font, and bitmap currently selected for use on a device. MFC provides classes for several different types of DCs, and an application must explicitly ask for a DC before drawing anything to a device, even simply writing some text on the video display.

Device contexts aren’t limited to physical devices, however; DCs can refer to logical devices as well. An example of a logical device is a metafile, which is a collection of structures that stores a picture in a device-independent format. Another example is a bitmap, a collection of pixels that represents some graphic image. You can draw on a bitmap or a metafile as easily as you can draw on a display or a printer.

Four types of device contexts are supplied by the Win32 API:

  Display contexts—Support graphics operations on a video display.
  Information contexts—Provide for the retrieval of device data.
  Memory contexts—Support graphics operations on a bitmap.
  Printer contexts—Support graphics operations on a printer or plotter.

The Graphics Device Interface

Device contexts are used extensively by the Graphics Device Interface (GDI), a main component of the Windows architecture. Non-MFC Windows programs written in Standard C/C++ use a device context to give Windows the specifics of the device it should draw on. This device context is sent as a parameter to any of several GDI function calls provided by the Windows API. The GDI provides all the basic drawing function-ality for Windows; the DC represents the device, providing a layer of abstraction that insulates your applications from the nastiness of drawing directly to hardware. (Figure 4.1 shows this hardware abstraction.) The GDI provides this insulation by calling the appropriate device driver in response to Windows graphics function calls. This abstraction frees you from having to write low-level driver code to support each device you’ll draw to—Windows includes drivers for and already knows how to draw hundreds of devices.


Figure 4.1  The layers of insulation between the hardware and an MFC application.

MFC Wrapping

When writing MFC programs, you get an added benefit when using DCs and the GDI functions: The GDI functions are built right into the MFC DC classes as DC methods. This makes using the GDI functions very convenient, especially with the Intellisense pop-up lists provided by Microsoft Visual C++ 6.0 (see Figure 4.2).


Figure 4.2  Visual C++ Intellisense pop-up lists make using device context methods easier than ever.

MFC Device Context Classes

MFC encapsulates the various types of device contexts provided by Windows into distinct DC classes that wrap a handle to a device context (HDC) within a C++ class. The class effectively contains information about the drawing attributes of a device. All drawing done in Windows is done on a device context, and all drawing methods are nicely wrapped into a DC object.

The following DC classes are the predefined MFC base classes for device contexts; they provide drawing and painting capabilities to MFC applications:

  CDC
  CPaintDC
  CClientDC
  CWindowDC
  CMetaFileDC

Figure 4.3 shows the relation of these classes in the MFC class hierarchy.


Figure 4.3  The hierarchy of MFC device context classes.

The Base Class: CDC

As you can see in Figure 4.3, the CDC class is the base class for the other DC classes. The CDC base class defines device context objects and provides methods for drawing on a display, a printer, or a window’s client area.

All graphic output should be rendered using the class methods provided by CDC. These class methods provide services for using drawing tools, manipulating device contexts, type-safe GDI object selection, manipulating color and palettes, coordinate mapping and conversion, working with polygons and regions, drawing shapes, drawing text, working with fonts, handling metafiles, and more. The CDC class is a monster that encapsulates the GDI functions that use device contexts.

It might surprise you to know that a CDC object actually contains not one, but two device contexts. These DCs are stored as the class members described in Table 4.1; they allow a CDC object to point to two different devices simultaneously. It is this very useful property of an extra DC within a CDC object that makes advanced MFC features such as print preview so easy to achieve.



Table 4.1 Class Members of a CDC Object

Class Member Description

m_hDC The output device context for a CDC object. Most CDC GDI calls that create output go to this member DC.
m_hAttribDC The attribute device context for this CDC object. Most CDC GDI calls requesting information from a CDC object are directed to this member DC.


Note:  

The two class member DCs initially refer to the same device, but they can be manipulated to refer to different devices at any time. Setting and releasing these DCs is accomplished through the CDC methods SetAttribDC(), SetOutputDC(), ReleaseAttribDC(), and ReleaseOutputDC().

The CDC::SetAttribDC() method sets m_hAttribDC (the attribute device context); SetOutputDC sets m_hDC (the output device context). Likewise, the CDC::ReleaseAttribDC() method releases m_hAttribDC, and the CDC::ReleaseOutputDC() method releases m_hDC.


The CDC methods are broken into several categories of functionality; these categories are reflected in other MFC device context classes, as described later in this chapter. The main categories of functionality are listed in Table 4.2.

Table 4.2 Functional Categories of CDC Methods

Category Purpose

Bitmap methods Methods for manipulating bitmaps and pixels.
Clipping methods Functions that define and manipulate clipping bounds for a device.
Color and color palette methods Methods for dealing with palette selection and realization and mapping logical colors.
Coordinate methods Methods for converting between device and logical units.
Device context methods Methods that retrieve information about a DC and its attributes.
Drawing attribute methods Methods for getting and setting colors and modes for a DC.
Drawing tool methods Methods for manipulating brush origins and enumerating the pens and brushes available in a DC.
Ellipse and polygon methods Methods for drawing ellipses and polygons.
Font methods Methods for retrieving font attributes.
Initialization methods Methods for creating and setting DC properties and retrieving information about the graphic objects within the DC.
Line output methods Methods for drawing lines on a DC.
Mapping methods Methods to set, retrieve, and manipulate origins and extents for windows and viewports, and to get and set mapping modes for a DC.
Metafile methods Methods to record and play metafiles.
Path methods Methods for manipulating paths in a DC. (Paths are created using GDI functions that generate curves and polygons.)
Printer escape methods Methods that access and manipulate printers and print jobs.
Region methods Methods for filling regions and manipulating region colors.
Simple drawing methods Methods that provide simple rectangle and icon drawing features.
Text methods Methods that output text and retrieve information about the font currently selected for the DC.
Type-safe selection methods Methods for selecting graphic objects or Windows stock objects.

The CDC class is very large, containing over 170 methods and data members. The following sections look at the basics of creating and using a DC.

Painting with Class CPaintDC

A CPaintDC object represents a drawing surface for a window. To fully understand the purpose of CPaintDC, let’s go back to the API level and see how things are done in SDK-land. Traditional C programs, as described in the Microsoft Win32 Software Development Kit (SDK), get a device context by using the Win32 API functions BeginPaint() and EndPaint() within the message handler for the Windows message WM_PAINT. The BeginPaint() function prepares a specified window for painting, fills a special PAINTSTRUCT structure with painting information, and returns a handle to a display device context (or painting DC). The EndPaint() function is always paired with a call to BeginPaint() to signify the end of painting in the specified window. The C code looks like this within the window procedure:

HDC          hDC;
PAINTSTRUCT  paintstruct;

switch (msg)
   case WM_PAINT:
   {
      hDC = BeginPaint(hwnd, &paintstruct);
      //
      // Perform graphics operations using hDC
      //
      EndPaint(hwnd, &paintstruct);
      return 0;
   }



A CPaintDC object performs these same steps—it just wraps them in an MFC class, and this makes your job considerably easier. The basic steps to follow when using a CPaintDC object are given here:

1.  Create a CPaintDC object.
2.  Draw on the CPaintDC object.
3.  Destroy the CPaintDC object.

The CPaintDC constructor automatically calls BeginPaint() and returns the paint DC to use for painting. Likewise, the destructor automatically calls EndPaint(). If you use the automatic storage class for your DC objects, you can forget about step 3 from the preceding list, making your job easier still.


Note:  

MFC removes much of the tedium involved in using CPaintDC by providing View classes that automate the process. A View class receives a CPaintDC object as a parameter of the CView::OnDraw() method. This CPaintDC is used for graphics methods and is destroyed by MFC when OnDraw() returns (the device contexts wrapped within the CPaintDC are released to Windows at this time).


Listing 4.1 gives a simple example of using a CPaintDC to draw graphics in a window—in this case, an ellipse. The result is shown in Figure 4.4. The window receives WM_PAINT messages from the MFC-provided window procedure in the window’s OnPaint() message handler. This member function must be added to the window’s message map to enable WM_PAINT message reception, like this:

// Message map for CMainWnd
BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
   ON_WM_ PAINT()
END_MESSAGE_MAP()

Listing 4.1 Using a CPaintDC Object to Draw an Ellipse in Response to WM_PAINT Messages in the Window’s OnPaint() Message Handler


///////////////////////////////////////////////////////////////////
// CMainWnd::OnPaint()

void CMainWnd::OnPaint()
{
   // Create a paint DC
   CPaintDC dc(this);
   // Draw an ellipse on the DC
   CRect rc;
   GetClientRect(&rc);
   dc.Ellipse(rc);
}


Figure 4.4  The ellipse resulting from the call to a CPaintDC object’s Ellipse() method.

Managing Client Areas with Class CClientDC

MFC supplies the CClientDC class (which is directly derived from CDC) to automate the process of calling and releasing a device context representing the client area of a window. The Win32 API function GetDC() is called in the CClientDC constructor; the corresponding API function ReleaseDC() is called in the destructor. Listing 4.2 gives a simple example of using a CClientDC to draw graphics in a window—in this case, a diamond (see Figure 4.5).


Figure 4.5  The diamond resulting from four successive calls to a CClientDC object’s CDC::LineTo() method.

Listing 4.2 Using a CClientDC Object to Draw a Diamond in Response to WM_LMBUTTONDOWN Messages in the Window’s OnLButtonDown() Message Handler


///////////////////////////////////////////////////////////////////
// CMainWnd::OnLButtonDown()

void CMainWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
   // Create a client DC to draw on
   CClientDC dc(this);
   // Draw a diamond on the DC
   CRect rc;
   GetClientRect(&rc);

   dc.MoveTo(0, (rc.bottom + rc.top) / 2);
   dc.LineTo((rc.right + rc.left) / 2, 0);
   dc.LineTo(rc.right, (rc.bottom + rc.top) / 2);
   dc.LineTo((rc.right + rc.left) / 2, rc.bottom);
   dc.LineTo(0, (rc.bottom + rc.top) / 2);
}

Managing Frame Windows with Class CWindowDC

MFC supplies the CWindowDC class (which is directly derived from CDC) to automate the process of calling and releasing a device context representing the entire surface of a window, including both the client and nonclient areas. The Win32 API function GetDC() is called in the CWindowDC constructor; the corresponding API function ReleaseDC() is called in the destructor.

Listing 4.3 gives a simple example of using a CWindowDC to draw graphics in a window—in this case, a series of ellipses in the title bar (see Figure 4.6). The ellipses clip everything in their path as they draw, obliterating all nonclient area components (System menu, window caption, minimize, maximize, and close buttons).


Figure 4.6  The series of circles across the main nonclient area results from successive calls to a CWindowDC object’s Ellipse() method.

Listing 4.3 Using a CWindowDC Object to Draw a Series of Circles in Response to WM_RMBUTTONDOWN Messages in the Window’s OnRButtonDown() Message Handler


///////////////////////////////////////////////////////////////////
// CMainWnd::OnRButtonDown()

void CMainWnd::OnRButtonDown(UINT nFlags, CPoint point)
{
   // Create a window DC to draw on
   CWindowDC dc(this);

   // Draw a series of ellipses on the DC,
   // in the window’s NC title bar area
   CRect rc;
   GetWindowRect(&rc);

   // Get the title bar height
   int cyCaption = GetSystemMetrics(SM_CYCAPTION);

   // Define the ellipse’s bounding rect
   CRect rcEllipse(0, 0, cyCaption, cyCaption);

   // Draw the ellipses
   while (rcEllipse.right < rc.right)
   {
      dc.Ellipse(rcEllipse);
      rcEllipse.left  += cyCaption;
      rcEllipse.right += cyCaption;
   }
}

Windows Graphic Objects

The display device contexts provided by Windows define logical display surfaces; the GDI provides drawing tools used to draw on the DC. MFC defines several types of graphic objects that correspond to the Windows drawing tools, including the following:

  Pens
  Brushes
  Fonts
  Bitmaps
  Palettes
  Regions

These Windows drawing tools are encapsulated by MFC graphic object classes. These classes are all derived from a common base class called CGdiObject (see Figure 4.7).


Figure 4.7  The location of graphic objects in the MFC class hierarchy.

The relation of the standard SDK drawing tool datatypes to the MFC graphic object classes is shown in Table 4.3.

Table 4.3 The Relation of the Standard SDK Drawing Tool Datatypes to MFC Graphic Object Classes

SDK Drawing Tool MFC Class Windows Datatype

Pen CPen HPEN
Brush CBrush HBRUSH
Font CFont HFONT
Bitmap CBitmap HBITMAP
Palette CPalette HPALETTE
Region CRgn HRGN



To use a graphic object in an MFC program, you’ll usually follow these three steps:

1.  Define a graphic object within a code block and initialize the object with a corresponding Create*() method. For example, to create a CPalette object, use CreatePalette().
2.  Select the new object into the current device context, typically by using the CDC::SelectObject() method. This method returns a pointer to the object being replaced (a pointer to a CGdiObject-derived object).
3.  When the graphic object has finished its task, use the CDC::SelectObject() method again to select the replaced graphic object back into the device context, leaving things as they were originally.


Note:  

A graphic object declared on the stack is automatically deleted when the object goes out of scope. Graphic objects declared as pointers and allocated with the new operator must be explicitly deleted after restoring the DC’s previous state.


The following sections look at each of these tools and how they’re used.


Note:  

Class CGdiObject provides the interface to the raw Win32 API in terms of graphics object handles and so on. You never create a CGdiObject object directly; you create an object from one of its derived classes (for example, CPen or CBrush).


Pens: Class CPen

A CPen object encapsulates a Windows GDI pen and provides several methods for working with CPen objects (see Table 4.4).

Table 4.4 Methods for Creating and Using CPen Objects

Method Description

CreatePen() Creates a logical pen with the specified style, width, and brush attributes and attaches it to a CPen object.
CreatePenIndirect() Creates a pen with the style, width, and color defined in a LOGPEN structure and attaches it to a CPen object.
FromHandle() Returns a pointer to a CPen object from a Windows HPEN.
GetExtLogPen() Gets a pen’s underlying EXTLOGPEN structure.
GetLogPen() Gets a pen’s underlying LOGPEN structure.
operator HPEN Returns the handle of the Windows pen currently attached to the CPen object.

You create a pen to use when drawing in a device context. Pens come in several styles and a rainbow of colors. A Windows pen is typically created and attached to a CPen object with the CPen::CreatePen() method, which uses this prototype:

BOOL CreatePen(int nPenStyle, int nWidth, COLORREF crColor);

The nPenStyle parameter can be any of the values shown in Table 4.5; the nWidth parameter is the width of the pen; the crColor parameter specifies the color of the pen.

Table 4.5 These Pen Styles Defined by the GDI Are Used for CPen Object Classes

Pen Style Description

PS_SOLID Creates a solid pen.
PS_DASH When the pen width is 1, creates a dashed pen.
PS_DOT When the pen width is 1, creates a dotted pen.
PS_DASHDOT When the pen width is 1, creates a pen with alternating dashes and dots.
PS_DASHDOTDOT When the pen width is 1, creates a pen with alternating dashes and double dots.
PS_NULL Creates a NULL (invisible) pen.
PS_INSIDEFRAME Creates a pen that draws inside the bounding rectangle of a Windows GDI shape such as an ellipse or a rectangle.

For example, to create and use a red, dashed pen, you would do this:

// Create a new red-dashed pen
CPen penRed;
penRed.CreatePen(PS_DASH, 1, RGB(255, 0, 0));

To select it into a device context, you would use the SelectObject() method of the DC, like this:

// Select the new pen into the device context, and save
// the old pen to restore on clean up...
CPen* ppenOld;
ppenOld = dc.SelectObject(&penRed);

Now any lines drawn in the window will have the red dashed style you just put into the DC. When you’re done drawing, restore the original pen by selecting it back into the DC, like this:

dc.SelectObject(ppenOld);

Brushes: Class CBrush

A CBrush object encapsulates a Windows GDI brush and provides several methods for working with CBrush objects (see Table 4.6).

Table 4.6 The Methods Provided by MFC for CBrush Objects

Method Description

CreateBrushIndirect() Creates a brush with the style, color, and pattern specified in a LOGBRUSH structure and attaches it to a CBrush object.
CreateDIBPatternBrush() Creates a brush with a pattern specified by a device-independent bitmap (DIB) and attaches it to a CBrush object.
CreateHatchBrush() Creates a brush with the specified hatched pattern and color and attaches it to a CBrush object.
CreatePatternBrush() Creates a brush with a pattern specified by a bitmap and attaches it to a CBrush object.
CreateSolidBrush() Creates a brush with the specified solid color and attaches it to a CBrush object.
CreateSysColorBrush() Creates a brush that is the default system color and attaches it to a CBrush object.
FromHandle() Returns a pointer to a CBrush object from a Windows HBRUSH object.
GetLogBrush() Gets the underlying LOGBRUSH structure from a CBrush object.
operator HBRUSH Returns the Windows handle attached to a CBrush object.



You create a brush to use when painting in a device context. Like pens, brushes come in several styles and a rainbow of colors. A Windows brush is most conveniently created and attached to a CBrush object with the constructor, CBrush::CBrush(), which uses these overloaded prototypes:

CBrush();
CBrush(COLORREF crColor);
CBrush(int nIndex, COLORREF crColor);
CBrush(CBitmap* pBitmap);

The crColor parameter specifies the color of the brush. The nIndex parameter specifies the pattern of the brush, which can be any of the values shown in Table 4.7. The pBitmap parameter is a pointer to a CBitmap object that contains a bitmap to use as a brush pattern.

Table 4.7 Hatch Styles Defined for GDI Brushes Are Used for CBrush Objects

Brush Style Description

HS_BDIAGONAL Downward hatch (left to right) at 45 degrees
HS_CROSS Horizontal and vertical crosshatch
HS_DIAGCROSS Crosshatch at 45 degrees
HS_FDIAGONAL Upward hatch (left to right) at 45 degrees
HS_HORIZONTAL Horizontal hatch
HS_VERTICAL Vertical hatch

Fonts: Class CFont

A CFont object encapsulates a Windows GDI font and provides several methods for working with CFont objects (see Table 4.8).

Table 4.8 CFont Class Methods for Creating and Manipulating Fonts

Method Description

CreateFontIndirect() Creates a CFont object with the characteristics given in a LOGFONT structure.
CreateFont() Creates a CFont object with the specified characteristics.
CreatePointFont() Creates a CFont object with the specified height (measured in tenths of a point) and typeface.
CreatePointFontIndirect() Creates a CFont object with the characteristics given in a LOGFONT structure; the font height is measured in tenths of a point instead of logical units.
FromHandle() Returns a pointer to a CFont object from a Windows HFONT.
operator HFONT() Returns the underlying Windows GDI font handle attached to a CFont object.
GetLogFont() Fills a LOGFONT with information about the logical font attached to a CFont object.

You create a font to use when drawing text in a device context. Fonts come in thousands of typefaces and styles. A Windows GDI font is typically created and attached to a CFont object with one of the first four methods listed in Table 4.8.

Bitmaps: Class CBitmap

A bitmap is an array of pixels that defines a graphic image—a picture or a pattern. The colors of the pixels are described by the data in the bitmap bits. The MFC class CBitmap encapsulates a handle to a Windows bitmap and provides some 14 methods and operators to create and manipulate bitmap objects.

Palettes: Class CPalette

A palette is a Windows GDI object that stores color information. A GDI palette object is basically a color lookup table used by the system to determine which colors to display on a 256-color palette display device. Windows defines 20 palette colors for the system, leaving 236 for applications to use for their own purposes. MFC encapsulates a Windows color palette within the CPalette class. Windows uses both logical palettes (which are lists of desired colors) and the system palette (which defines color slots currently available in the hardware palette). The CPalette class provides nine methods and one operator for creating and manipulating CPalette objects.


Note:  

The terms hardware palette and system palette are used interchangeably even though, technically, the system palette is really a copy of the values found in the hardware palette. The Windows Palette Manager uses the system palette values.


Regions: Class CRgn

A region is a Windows GDI object that stores polygonal or elliptical information about regions on a display device. The CRgn class encapsulates a GDI region object and provides several methods for creating and working with regions. These methods are described in Table 4.9.

Table 4.9 Class CRgn Methods for Creating and Using Regions

Method Description

CombineRgn() Creates a union of two specified CRgn objects.
CopyRgn() Copies one CRgn object into another.
CreateEllipticRgn() Creates a CRgn object with an elliptical region.
CreateEllipticRgnIndirect() Creates a CRgn object with an elliptical region defined by a RECT structure as its bounding box.
CreateFromData() Creates a region based on a given region and geometrical transformation data.
CreateFromPath() Creates a region based on a path that’s selected into a given device context.
CreatePolygonRgn() Creates a CRgn object with a polygonal region. Polygon regions are automatically closed, if necessary, by connecting the last vertex of the polygon to the first.
CreatePolyPolygonRgn() Creates a CRgn object with a region made up of a series of closed polygons.
CreateRectRgn() Creates a CRgn object with a rectangular region.
CreateRectRgnIndirect() Creates a CRgn object with a rectangular region defined by a RECT structure.
CreateRoundRectRgn() Creates a CRgn object with a rounded corner rectangular region.
EqualRgn() Creates two CRgn objects to determine whether they are equivalent.
FromHandle() Returns a pointer to a CRgn object from a handle to a Windows region.
GetRegionData() Fills a buffer with data that describes a given region.
GetRgnBox() Gets the coordinates of a CRgn object’s bounding rectangle.
OffsetRgn() Moves a CRgn object by the specified offsets.
PtInRegion() Determines whether a specified point lies within a region.
RectInRegion() Determines whether any part of a specified rectangle lies within the boundaries of a region.
SetRectRgn() Sets an existing CRgn object to a specified rectangular region.
operator HRGN Returns the Windows handle wrapped in the CRgn object.



GDI Coordinate Systems

The GDI supports two types of coordinate systems: physical and logical. The physical coordinate system is that of the physical device, such as a video display. A window on the display starts at the origin—location (0,0)—at the upper-left corner of the display, with the x-axis increasing going to the right and the y-axis increasing going down. The lower-right corner of the window corresponds with the lower-right corner of the display (see Figure 4.8), but the actual numbers for a video display depend on the current video resolution. Typical locations are (640, 480), (800, 600), or (1024, 768).


Figure 4.8  The physical (device) coordinate system.


Tip:  

Use the Win32 API function GetSystemMetrics() to find the actual display size in pixels.

Use GetSystemMetrics(SM_CXSCREEN) to get the screen width; use GetSystemMetrics(SM_CYSCREEN) to get the screen height.


There are several logical coordinate systems, and Windows maps coordinates on the current logical display to the physical device before displaying any graphical output. Each of the GDI functions—and therefore the MFC wrapper methods in class CDC—use logical coordinates. The resulting output can vary depending on a device context’s current mapping mode. Mapping modes allow an application to send GDI graphics output to a logical window. The GDI then maps the output to the physical window or some other device (such as a printer).

Logical Mapping Modes

Windows uses mapping modes to project a logical coordinate system into the physical coordinate system. The origin of a logical window defines the upper-left corner of the window. All other points are referenced from the origin. The mapping mode defines the length of a logical unit of measure used when converting logical units to physical device units. The mapping mode also determines the orientation of the device’s x and y axes. The GDI uses a DC’s current mapping mode to convert logical coordinates into the corresponding device coordinates. Table 4.10 lists the logical mapping modes supported by Windows.

Table 4.10 The Logical Mapping Modes Supported by Windows

Mapping Mode Description

MM_ANISOTROPIC The mapping between logical and physical coordinates uses variably scaled axes; images in the logical window can be stretched in any direction when redrawn. This mode doesn’t change the current window or viewport settings. Use the CDC::SetWindowExt() and CDC::SetViewportExt() methods to change units, orientation, and scaling. The positive x-axis is to the right and the positive y-axis goes up.
MM_HIENGLISH Each unit in the logical window is 0.001 inch long. The positive x-axis is to the right and the positive y-axis goes up.
MM_HIMETRIC Each unit in the logical window is 0.01 millimeter long. The positive x-axis is to the right and the positive y-axis goes up.
MM_ISOTROPIC The mapping between logical and physical coordinates uses equally scaled axes; images in the logical window can be resized in any direction but retain the same aspect ratio when redrawn. To make sure that both x and y units remain the same size, the GDI adjusts sizes as necessary. Setting the mapping mode to MM_ISOTROPIC doesn’t change the current window or viewport settings. Use the SetWindowExt() and SetViewportExt() methods to change units, orientation, and scaling. The positive x-axis is to the right and the positive y-axis goes up.
MM_LOENGLISH Each logical unit represents 0.01 millimeters. The positive x-axis is to the right and the positive y-axis goes up.
MM_LOMETRIC Each logical unit represents 0.1 millimeter. The positive x-axis is to the right and the positive y-axis goes up.
MM_TEXT Each logical unit represents 1 device pixel. The positive x-axis is to the right and the positive y-axis goes down.
MM_TWIPS Each logical unit represents 1/20 of a point. Because a point is 1/72 inch, that makes a twip 1/1440 inch. The positive x-axis is to the right and the positive y-axis goes up.


Tip:  

It’s a good idea to clearly distinguish between logical and physical devices when using Win32 API functions and their MFC wrapper methods. In general, logical coordinates are used for most drawing functions; physical coordinates are used in window management functions (such as SetWindowPos()).


Vector Graphics

Many types of vector graphic output functions are available for your applications from the Win32 GDI. These functions fall into two basic categories:

  Lines and curves
  Closed figures

These categories can further be broken into the following, more detailed, categories of Win32 GDI functions, which are reflected in CDC class methods:

  Lines
  Ellipses
  Rectangles and regions
  Polygons
  Bézier curves
  Metafiles
  Fonts and text output

Drawing Modes

Windows provides several drawing modes. A drawing mode specifies how the colors combine between the current pen and objects already present on the display surface. The drawing modes represent all possible Boolean combinations of two variables. They use the binary operators AND, OR, and XOR, and the unary operation NOT in the binary raster-operation codes listed in Table 4.11.

The drawing mode is set using the CDC::SetRop2() method, which has the following prototype:

int SetROP2( int nDrawMode );

The nDrawMode parameter specifies the new drawing mode, which can be any of the values in Table 4.11. This method returns the previous drawing mode.



Table 4.11 The Drawing Modes Defined by Win32 Represent All Possible Boolean Combinations of Two Variables

Mode Identifier Description

R2_BLACK The pixel color is always black.
R2_COPYPEN The pixel is the color of the pen.
R2_MASKNOTPEN The pixel color is a combination of the colors common to both the screen and the inverse of the pen (final pixel = [NOT pen] AND screen pixel).
R2_MASKPEN The pixel color is a combination of the colors common to both the pen and the screen (final pixel = pen AND screen pixel).
R2_MASKPENNOT The pixel color is a combination of the colors common to both the pen and the inverse of the screen (final pixel = [NOT screen pixel] AND pen).
R2_MERGENOTPEN The pixel color is a combination of the screen color and the inverse of the pen color (final pixel = [NOT pen] OR screen pixel).
R2_MERGEPEN The pixel color is a combination of the pen color and the screen color (final pixel = pen OR screen pixel).
R2_MERGEPENNOT The pixel color is a combination of the pen color and the inverse of the screen color (final pixel = [NOT screen pixel] OR pen).
R2_NOP The pixel color stays the same.
R2_NOT The pixel color is the inverse of the screen color.
R2_NOTCOPYPEN The pixel color is the inverse of the pen color.
R2_NOTMASKPEN The pixel color is the inverse of the R2_MASKPEN color (final pixel = NOT[pen AND screen pixel]).
R2_NOTMERGEPEN The pixel color is the inverse of the R2_MERGEPEN color (final pixel = NOT[pen OR screen pixel]).
R2_NOTXORPEN The pixel color is the inverse of the R2_XORPEN color (final pixel = NOT[pen XOR screen pixel]).
R2_WHITE The pixel color is always white.
R2_XORPEN The pixel color is a combination of the colors that are in the pen or in the screen, but not in both (final pixel = pen XOR screen pixel).


Note:  

The program RASTER1.EXE and its source code on the CD-ROM show how to use each of these raster operations and the results of using each.


Figure 4.9 shows the results of using each of the named raster operations in the sample program RASTER1.EXE.


Figure 4.9  The results of using each of the named raster operations to combine two ellipses into a single image.

Points

Windows drawing functions typically take location parameters as x and y coordinates defining a point. Windows defines a point in the POINT structure (see windef.h) that looks like this:

typedef struct tagPOINT
{
  LONG x;
  LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

MFC wraps the POINT structure with the CPoint class structure that provides the methods shown in Table 4.12. Note that the CPoint class isn’t derived from CObject like most other MFC classes, but from the tagPOINT typedef structure just shown.

Table 4.12 CPoint Class Methods and Operators

Method Description

Offset() Adds values to the x and y members of the CPoint object.
operator == Checks two points for equality.
operator != Checks two points for inequality.
operator += Offsets a CPoint object by adding a size or point.
operator = Offsets a CPoint object by subtracting a size or point.
operator + Returns the sum of a CPoint object and a size or point, or returns a CRect offset by a size.
operator - Returns the difference of a CPoint object and a size (or the negation of a point), or returns a CRect offset by a negative size, or returns the size difference between two points.

As you can see, the CPoint class offers numerous operators to make point calculations easier.



Drawing Points

To draw a single point on a device context, call the CDC::SetPixel() method using this prototype:

COLORREF SetPixel(POINT point, COLORREF crColor);

In this syntax, point specifies the raster display pixel to set, and crColor specifies the color of pixel.

Lines and Polylines

Lines are drawn on a DC using the CDC methods MoveTo(), LineTo(), and PolyLine(). These methods all use the pen currently selected into the DC for their drawing.


Note:  

In MFC, the CPen object currently selected into a device context is the one that controls line styles, colors, and thickness. Windows defines the following six pen styles in WINGDI.H:

#define PS_SOLID        0     // _______
#define PS_DASH         1     // -------
#define PS_DOT          2     // .......
#define PS_DASHDOT      3     // _._._._
#define PS_DASHDOTDOT   4     // _.._.._
#define PS_NULL         5

Setting the Current Drawing Position

The CDC::MoveTo() method sets the current drawing position (current position). CDC provides two overloaded prototypes for this method to make setting the current position easy:

CPoint MoveTo(int x, int y);
CPoint MoveTo(POINT point);

This means that you can pass the x and y coordinates for the new current position separately or in a POINT structure (or in a CPoint object).

Drawing a Single Line

The CDC::LineTo() method draws a line from the current position up to, but not including, the point specified by either x and y, or by a point. For each case, CDC again provides overloaded methods:

BOOL LineTo(int x, int y);
BOOL LineTo(POINT point);

The code fragment in Listing 4.4 shows how the VECTEXT1 program displays all six pen line-styles (pDC is assumed to be a valid pointer to a CClientDC). Figure 4.10 shows the result of this code; note that the sixth style is PS_NULL, the invisible pen, and doesn’t appear at all.


Figure 4.10  Drawing all six line-styles in the VECTEXT1 program.

Listing 4.4 Drawing Lines Using All Six Pen Line-styles


// Six different pen styles
int cy = 25;
for (int nLineStyle = 0; nLineStyle < 6; nLineStyle++)
{
   // Create a new pen
   CPen penBlue;

   // Pick the current pen style
   penBlue.CreatePen(nLineStyle, 1, crBlue);

   // Select the new pen into the device context, and save
   // the old pen to restore on clean up...
   CPen* ppenOld;
   ppenOld = pDC->SelectObject(&penBlue);

   // Draw on the DC
   pDC->MoveTo(10, cy);
   pDC->LineTo(10 + nLineLength, cy);
   cy += 20;

   // Leave things as you found them (clean up)
   pDC->SelectObject(ppenOld);
}

Drawing a Polyline

The CDC::PolyLine() method draws a set of line segments connecting the points specified in an array of points. The lines use the current pen, but unlike the CDC::LineTo() method, the CDC::PolyLine() method doesn’t use or update the current position. The prototype for CDC::PolyLine() is shown here:

BOOL Polyline(LPPOINT lpPoints, int nCount);

In this syntax, lpPoints is a pointer to an array of points, and nCount is the number of points in the array.

In the VECTEXT1 program, class CMainWnd defines an array of CPoint objects as the member array variable m_apt. This array is initialized to the following values:

// Define an array of points
m_apt[0].x = 200; m_apt[0].y = 420;
m_apt[1].x = 200; m_apt[1].y = 200;
m_apt[2].x = 375; m_apt[2].y = 300;
m_apt[3].x = 480; m_apt[3].y = 310;
m_apt[4].x = 590; m_apt[4].y = 350;
m_apt[5].x = 465; m_apt[5].y = 200;
m_apt[6].x = 320; m_apt[6].y = 150;
m_apt[7].x = 205; m_apt[7].y = 100;
m_apt[8].x = 115; m_apt[8].y = 150;
m_apt[9].x = 100; m_apt[9].y = 175;

The DoPolyLine() method in Listing 4.5 shows how the VECTEXT1 program draws a polyline using the CMainWnd class member array of points: m_apt[10] (pDC is assumed to be a valid pointer to a CClientDC). This code produces the result shown in Figure 4.11.


Figure 4.11  A polyline from 10 points in the VECTEXT1 program.

Listing 4.5 Drawing a Polyline with Visible Vertices: the VECTEXT1 CMainWnd::DoPolyLine() Method


void CMainWnd::DoPolyLine(CClientDC* pDC)
{
   // Create a label
   CString str = “A polyline with 10 vertices:”;
   pDC->TextOut(5, 5, str);

   // Draw points
   for (int i = 0; i < 10; i++)
      pDC->Ellipse(m_apt.x - 4, m_apt.y - 4,
                   m_apt.x + 4, m_apt.y + 4);

   // Create a new pen
   CPen penRed;
   penRed.CreatePen(PS_SOLID, 1, crRed);

   // Select a new pen into the device context, and save
   // the old pen to restore on clean up...

   CPen* ppenOld;
   ppenOld = pDC->SelectObject(&penRed);

   // Draw lines with red pen to connect vertex points
   pDC->Polyline(m_apt, 10);

   // Leave things as you found them (clean up)
   pDC->SelectObject(ppenOld);
}

Rectangles

Like the POINT structure, the rectangle structure (RECT) is one of the most important in Windows. RECT structures are used to store window coordinates and sizes, and as parameters for many MFC methods and Win32 API functions. The following RECT structure defines the position and size of a logical rectangle:

typedef struct tagRECT
{
   LONG    left;
   LONG    top;
   LONG    right;
   LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

MFC wraps the RECT structure within the CRect class that provides the methods and operators listed in Table 4.13.

Table 4.13 CRect Class Methods and Operators

Method Description

BottomRight() Returns the bottom-right point of a CRect object.
CenterPoint() Returns the center point of a CRect object.
CopyRect() Copies the dimensions of a source rectangle to a CRect object.
DeflateRect() Decreases the width and height of a CRect object.
EqualRect() Compares the coordinates of a CRect object and a given rectangle for equality.
Height() Returns the height of a CRect object.
InflateRect() Increases the width and height of a CRect object.
IntersectRect() Returns a CRect object defined by the area of intersection between two rectangles.
IsRectEmpty() Determines whether a CRect object is empty (if width or height are zero).
IsRectNull() Determines whether the top, bottom, left, and right member variables all equal zero (NULL).
NormalizeRect() Normalizes the height and width of a CRect object.
OffsetRect() Moves a CRect object by the specified offset values.
PtInRect() Tests whether a specified point lies within a CRect object.
SetRect() Sets the size of a CRect object.
SetRectEmpty() Sets all coordinates of a CRect object to zero, making an empty rectangle.
Size() Returns the size of a CRect object.
SubtractRect() Subtracts one rectangle from another.
TopLeft() Returns the top-left point of a CRect object.
UnionRect() Returns a CRect object defined by the area of union of two rectangles.
Width() Calculates the width of a CRect object.
operator LPCRECT Converts a CRect object to an LPCRECT.
operator LPRECT Converts a CRect object to an LPRECT.
operator = Copies the size and position of a rectangle to a CRect object.
operator == Tests the bounding coordinates of two CRect objects to see whether they are the same.
operator != Tests the bounding coordinates of two CRect objects to see whether they are not the same.
operator += Adds the specified offset values to a CRect object or inflates the object.
operator = Subtracts the specified offset values from a CRect object or deflates the object.
operator &= Sets a CRect object equal to the intersection of the object and a rectangle.
operator |= Sets a CRect object equal to the union of the object and a rectangle.
operator + Adds the given offset values to a CRect object or inflates the object and returns the resulting CRect.
operator Subtracts the given offset values from a CRect object or deflates the object and returns the resulting CRect.
operator & Creates the intersection of a CRect object and a rectangle and returns the resulting CRect.
operator | Creates the union of a CRect object and a rectangle and returns the resulting CRect.



As you can see, the CRect class offers many useful methods that return the length, width, and coordinates of a rectangle and that allow you to move, resize, intersect, and combine rectangles. There are also many operators for working with CRect objects.

Drawing Rectangles

Regular rectangles (those with square corners) are typically drawn on a DC using the CDC methods Rectangle() and FillRect(). The CDC::Rectangle() method uses overloaded prototypes to specify the rectangle to draw in two ways:

BOOL Rectangle(int x1, int y1, int x2, int y2);
BOOL Rectangle(LPCRECT lpRect);

In this syntax, x1, y1, x2, and y2 are the coordinates of a rectangle, and lpRect is a pointer to a rectangle. This method draws the outline of a rectangle with the current pen and fills it with the current brush.

The CDC::FillRect() method doesn’t draw a rectangle border, it just fills the rectangle with the current brush. This brush can be of any type (bitmap, dithered, and so on). The prototype for this method is shown here:

void FillRect(LPCRECT lpRect, CBrush* pBrush);

In this syntax, lpRect is a pointer to a rectangle, and pBrush is a pointer to a brush. To fill a rectangle with a solid COLORREF color, call the CDC::FillSolidRect() method using this prototype:

void FillSolidRect(LPCRECT lpRect, COLORREF clr);

In addition to the regular square-cornered rectangles, Windows provides the rounded rectangle—a rectangle with rounded corners. To draw a rectangle with rounded corners, call the CDC::RoundRect() method using this prototype:

BOOL RoundRect(LPCRECT lpRect, POINT point);

In this syntax, lpRect is a pointer to a rectangle, and point is the offset from the upper-left corner that defines the amount of fillet (rounding) applied to the rounded corners. Figure 4.12 shows the result of using a point offset of CPoint(50, 50).

// Draw a rounded rect on the DC
pDC->RoundRect(arcSection[x][y], CPoint(50, 50));


Figure 4.12  Rounded rectangles created with CDC::RoundRect() in the VECTEXT1 program.

Regions

A region is a Windows graphic object that represents an area of the device context’s work space. This area can be composed of rectangles, ellipses, and polygons. Regions can be filled with the current brush or used to determine the clipping region of the DC (the area where drawing takes place). The clipping region can be any shape derived from the union, difference, or intersection of rectangles, ellipses, and polygons.

A region is created with one of the CDC region-creation methods such as CDC::CreateRectRgn() or CDC::CreatePolygonRgn(). A region can be displayed using the methods CDC::FillRgn(), CDC::FrameRgn(), CDC::InvertRgn(), and CDC::PaintRgn() and must be destroyed using the CDC::DeleteObject() method after you’re done using it. The CDC methods for creating regions are listed in Table 4.14.

Table 4.14 The Methods Provided by Class CDC for Creating Regions

Method Description

CombineRgn() Creates a union of two specified CRgn objects and assigns the result of the union to a third CRgn object.
CopyRgn() Copies a CRgn object and assigns the result to a second CRgn object.
CreateEllipticRgn() Creates an elliptical region and attaches it to a CRgn object.
CreateEllipticRgnIndirect() Creates an elliptical region defined by a RECT structure and attaches it to a CRgn object.
CreateFromData() Creates a region from the given region and transformation data.
CreateFromPath() Creates a region from the path that is selected into the given device context.
CreatePolygonRgn() Creates a polygonal region and attaches it to a CRgn object. If needed, Windows automatically closes the polygon by drawing a line from the last vertex to the first.
CreatePolyPolygonRgn() Creates a region consisting of a series of closed polygons and attaches it to a CRgn object. The polygons can be disjointed, or they can overlap.
CreateRectRgn() Creates a rectangular region and attaches it to a CRgn object.
CreateRectRgnIndirect() Creates a rectangular region defined by a RECT structure and attaches it to a CRgn object.
CreateRoundRectRgn() Creates a rectangular region with rounded corners and attaches it to a CRgn object.

The CDC methods for working with regions are listed in Table 4.15.

Table 4.15 The Methods Provided by Class CDC for Working with Regions

Method Description

EqualRgn() Tests two CRgn objects for equivalency.
FromHandle() Returns a pointer to a CRgn object from a handle to an existing Windows region.
GetRegionData() Fills a buffer with data describing the given region.
GetRgnBox() Gets the coordinates of the bounding rectangle of a CRgn object.
OffsetRgn() Moves a CRgn object according to the specified offsets.
PtInRegion() Determines whether a specified point lies within the region.
RectInRegion() Determines whether any part of a specified rectangle lies within the bounding rectangle of a CRgn object.
SetRectRgn() Sets an existing CRgn object to a specified rectangular region.



The CRgn::CombineRgn() method uses Boolean operations to create a new region. The prototype for this method is as follows:

int CombineRgn(CRgn* pRgn1, CRgn* pRgn2, int nCombineMode);

In this syntax, the parameters pRgn1 and pRgn2 are pointers to existing CRgn objects, and the nCombineMode parameter is one of the Boolean operations listed in Table 4.16. These operation values are defined by the Win32 API for combining regions.

Table 4.16 The Boolean Operations Provided by the Win32 API and the MFC Class CRgn

Boolean Value Description

RGN_AND Uses overlapping areas of both regions (intersection).
RGN_COPY Creates a copy of the first region.
RGN_DIFF Creates a region consisting of the areas of region 1 (identified by pRgn1) that are not part of region 2 (identified by pRgn2).
RGN_OR Combines both regions in their entirety (union).
RGN_XOR Combines both regions but removes overlapping areas.

These operations are explored a bit in the VECTEXT1 program.

Polygons

The CDC::Polygon() method draws a closed polygon with the current pen, filled by the current brush. The points specified in an array of points are connected to create the polygon, and the last vertex is connected to the first if necessary. The prototype for CDC::Polygon() is as follows:

BOOL Polygon(LPPOINT lpPoints, int nCount);

In this syntax, lpPoints is a pointer to an array of points, and nCount is the number of points in the array.

The DoPolygon() method in the VECTEXT1 program is very similar to the DoPolyLine() method; the difference is a call to CDC::Polygon() instead of CDC::Polyline(). Figure 4.13 shows the result of using the array of points in the class member m_apt[10] to draw the polygon with 10 vertices.


Figure 4.13  Drawing a closed polygon in the VECTEXT1 program.

Ellipses

Ellipses use rectangles as bounding boxes, and the CDC::Ellipse() method takes a rectangle as a parameter. The CMainWnd::DoEllipses() method from the VECTEXT1 program (shown in Listing 4.6) divides the client area into six equal logical rectangles and uses them as bounding boxes for six ellipses. Each ellipse has one of the six different hatch brush styles. The result of this code is shown in Figure 4.14.

The CDC::Ellipse() method draws an ellipse with the current pen, filled by the current brush. There are two overloaded prototypes for CDC::Ellipse():

BOOL Ellipse(int x1, int y1, int x2, int y2);
BOOL Ellipse(LPCRECT lpRect);

In this syntax, x1, y1, x2, and y2 are the corners of a bounding rectangle for the ellipse. The lpRect parameter is a pointer to a bounding rectangle for the ellipse.

The DoEllipses() method in the VECTEXT1 program uses various hatch brush styles to fill ellipses in the client area. Figure 4.14 shows the result of using these hatch brushes.


Figure 4.14  Drawing ellipses with various hatch brush styles in the VECTEXT1 program.

Listing 4.6 The Code to Draw Six Ellipses with the Six Different Hatch Brush Styles


///////////////////////////////////////////////////////////////////
// CMainWnd::DoEllipses()

void CMainWnd::DoEllipses(CClientDC* pDC)
{
   // Create a label
   CString str = “Ellipses filled with hatch brushes:”;
   pDC->TextOut(5, 5, str);

   // Subdivide the display into 6 sections
   CRect  rc;
   CRect  arcSection[4][3];

   GetClientRect(&rc);
   int cx = rc.right / 3;
   int cy = (rc.bottom - 25) / 2;

   int nHeight = cy;

   for (int x = 0; x < 2; x++)
   {
      int nWidth = cx;
      for (int y = 0; y < 3; y++)
      {
         arcSection[x][y].left   = nWidth - cx;
         arcSection[x][y].top    = nHeight - cy + 25;
         arcSection[x][y].right  = arcSection[x][y].left + cx;
         arcSection[x][y].bottom = arcSection[x][y].top + cy;

         nWidth += cx;
      }
      nHeight += cy;
   }

   // Six different hatch brush styles
   cy = 25;
   int nHatchStyle = 0;

   for (x = 0; x < 2; x++)
   {
      for (int y = 0; y < 3; y++)
      {
         /*==================================================
            From WINGDI.H:

            // Hatch Styles
            #define HS_HORIZONTAL    0    // -----
            #define HS_VERTICAL      1    // |||||
            #define HS_FDIAGONAL     2    // \\\\\
            #define HS_BDIAGONAL     3    // /////
            #define HS_CROSS         4    // +++++
            #define HS_DIAGCROSS     5    // xxxxx
         ===================================================*/

         // Create a hatch brush
         CBrush br(nHatchStyle, crRed);

         // Select the new brush into the device context, and save
         // the old brush to restore on clean up...
         CBrush* pbrOld;
         pbrOld = pDC->SelectObject(&br);

         // Draw an ellipse on the DC
         pDC->Ellipse(arcSection[x][y]);
         nHatchStyle++;

         // Leave things as you found them (clean up)
         pDC->SelectObject(pbrOld);
      }
   }
}

Bézier Curves

A Bézier curve is a parametric curve (or spline curve) defined by a set of control points. Blending functions are applied to groups of control points to calculate the curve. MFC provides a third-order Bézier blending function in the form of the CDC::PolyBezier() and CDC::PolyBezierTo() methods. The prototype for the CDC::PolyBezier() method is as follows:

BOOL PolyBezier(const POINT* lpPoints, int nCount);

In this syntax, lpPoints is a pointer to an array of POINT data structures (or CPoint objects) that contains the endpoints and control points of the splines, and nCount specifies the number of points in the lpPoints array.


Note:  

The value must be one more than three times the number of splines to be drawn, because each Bézier spline requires two control points and an endpoint, and the initial spline requires an additional starting point.


The code in Listing 4.7 shows how the VECTEXT1 program uses the array of points in the m_apt[10] class member to draw a static Bézier curve composed of three splines. The result of the DoBezier() method can be seen in Figure 4.15.


Figure 4.15  Drawing a static Bézier curve in the VECTEXT1 program.



Listing 4.7 The DoBezier() Method from VECTEXT1 Draws a Series of Bézier Curves and Control Points


void CMainWnd::DoBezier(CClientDC* pDC)
{
   // Create a label
   CString str = “Bezier with ten control points:”;
   pDC->TextOut(5, 5, str);

   // Draw points
   for (int i = 0; i < 10; i++)
      pDC->Ellipse(m_apt.x - 4, m_apt.y - 4,
                   m_apt.x + 4, m_apt.y + 4);

   // Create new pens
   CPen penBlue;
   CPen penRed;

   penBlue.CreatePen(PS_DOT, 1, crBlue);
   penRed.CreatePen(PS_SOLID, 1, crRed);

   // Select a new pen into the device context, and save
   // the old pen to restore on clean up...
   CPen* ppenOld;
   ppenOld = pDC->SelectObject(&penBlue);

   // Draw lines with blue pen to connect control points
   pDC->Polyline(m_apt, 10);

   // Draw Bezier curve with red pen
   pDC->SelectObject(&penRed);
   pDC->PolyBezier(m_apt, 10);

   // Leave things as you found them (clean up)
   pDC->SelectObject(ppenOld);

}

Fonts and Text

Text is very important to users of computer programs, even users of programs created for a GUI OS like Windows. In fact, text output is one of the most important features of the operating system. Since the days of Windows 3.1, Windows programs have been able to access a wonderful technology called the TrueType font (TTF). TrueType fonts are vector fonts scaleable to virtually any size with no degradation of image quality.


Note:  

To get you started working with fonts, some font terminology should be explained. The term typeface refers to the style of lettering and the visual appearance of the text. The term font refers to a set of characters in a given typeface (for example, Arial). In font terminology, a point is 1/72 of an inch and is used to measure the height of font characters.


Font Characteristics

Two main categories of fonts are used in Windows: fixed width (or monospace) and variable width. Monospace font characters are all the same width; variable-width font characters each take up as much space as they need. Windows defines three different types of fonts: raster fonts (bitmapped fonts), vector fonts (fonts composed of a series of line segments), and TrueType (fonts that use lines and spline curves to define character outlines).

Font Families

All typefaces are grouped into font families that represent the style (or mood) of the font. Identifiers for the default families are found in the header file WINGDI.H; these font family (FF_*) identifiers are listed in Table 4.17.

Table 4.17 Font Families Provided by Windows

Family Name Description

FF_DECORATIVE This is a novelty font family. One example is Viking Runes.
FF_DONTCARE This is a generic family name used when no information about a font is needed.
FF_MODERN This is a monospace font with or without serifs. Monospace fonts are usually modern; examples include Pica, Elite, and Courier New.
FF_ROMAN Specifies a proportional font with serifs. An example is Times New Roman.
FF_SCRIPT Specifies a font designed to look like handwriting; examples include Script and Cursive.
FF_SWISS Specifies a proportional font without serifs; an example is Arial.

These IDs are used when creating, selecting, or getting information about a font. Fonts can have various point sizes and styles (like bold or italics). This book is concerned mainly with the TrueType variety because it’s the most versatile and is relatively easy to use (even if it is somewhat complex).

The TEXTMETRIC Structure

To output text, Windows uses a bounding rectangle and paints a representation of the current character to the display pixels as a bitmap. There are Win32 API functions and MFC methods that use a Windows TEXTMETRIC structure to get information about text characters on the display.

The TEXTMETRIC structure contains information about a display font and gives size information in logical units that depend on the current mapping mode. The TEXTMETRIC structure is shown in Listing 4.8.

Listing 4.8 The Windows TEXTMETRIC Structure


typedef struct tagTEXTMETRIC
{
    LONG tmHeight;            // Character height (ascent + descent)
    LONG tmAscent;            // Character ascent above base line
    LONG tmDescent;           // Character descent below base line
    LONG tmInternalLeading;   // Extra space inside tmHeight
    LONG tmExternalLeading;   // Extra space between text rows
    LONG tmAveCharWidth;      // Average width of font characters
    LONG tmMaxCharWidth;      // Width of the widest font character
    LONG tmWeight;            // The weight of the font
    LONG tmOverhang;          // Extra width padding
    LONG tmDigitizedAspectX;  // Horizontal aspect of target device
    LONG tmDigitizedAspectY;  // Vertical aspect of target device
    BCHAR tmFirstChar;        // Char value of first font character
    BCHAR tmLastChar;         // Char value of last font character
    BCHAR tmDefaultChar;      // Default substitution character
    BCHAR tmBreakChar;        // Char value for word break formats
    BYTE tmItalic;            // An italic font if it’s nonzero
    BYTE tmUnderlined;        // An underlined font if it’s nonzero
    BYTE tmStruckOut;         // A strikeout font if it’s nonzero
    BYTE tmPitchAndFamily;    // Font pitch and family information
    BYTE tmCharSet;           // Character set of the font
} TEXTMETRIC;


Note:  

In addition to the TEXTMETRIC structure, there is a NEWTEXTMETRIC structure defined to hold additional information about TrueType fonts.


To get basic information about the font currently selected into a device context, you can use the CDC::GetTextMetrics() method. The prototype for this method is shown following:

BOOL GetTextMetrics(LPTEXTMETRIC lpMetrics) const;

In this syntax, lpMetrics is a pointer to a TEXTMETRIC structure that receives the text metrics. For example, to find the average width of the font currently in a given device context, simply call GetTextMetrics(), as in this code fragment:

TEXTMETRIC tm;
if (dc.GetTextMetrics(&tm))
   int nAveCharWidth = tm.tmAveCharWidth;



The VECTEXT1 program displays complete TEXTMETRIC information about the Windows default system font by calling the CMainWnd::DoTextSystem() method (see Listing 4.9). Figure 4.16 shows the result of this method.


Figure 4.16  Displaying complete TEXTMETRIC information about the Windows default system font.

Listing 4.9 Displaying System Font TEXTMETRIC Information in the VECTEXT1 program’s CMainWnd::DoTextSystem() Method


void CMainWnd::DoTextSystem(CClientDC* pDC)
{
   // Create a label
   CString strLbl = “Generic text information on the system font:”;
   pDC->TextOut(5, 5, strLbl);

   // Select the system font into the DC
   HFONT hFont = (HFONT)::GetStockObject(SYSTEM_FONT);
   CFont fnt;
   CFont* pFont = fnt.FromHandle(hFont);
   CFont* pfntOld = pDC->SelectObject(pFont);

   // Get some info about the current font
   TEXTMETRIC tm;
   pDC->GetTextMetrics(&tm);

   UINT nCurLineHeight = 40;

   // Create an array of strings to display the TEXTMETRIC data
   CString str[42];

   // Label strings
   str[0] = “Height”;
   //
   // etc. for all label strings
   //
   str[20] = “CharSet”;

   // Data strings
   str[21].Format(“ = %d”, tm.tmHeight);
   //
   // etc. for all data strings
   //
   str[41].Format(“ = %d”, tm.tmCharSet);

   // Display strings 0-20
   for (int i = 0; i < 21; i++)
   {
      pDC->TextOut(20, nCurLineHeight, str);
      nCurLineHeight += tm.tmHeight;
   }

   // Reset line height
   nCurLineHeight = 40;

   //
   // Now you need to determine the distance that the data strings
   // should move to the right to align the data evenly.
   //
   // str[9] is the longest string, so you calculate the width + 2
   // to space the = signs nicely.
   //

   UINT nLeft = 20 + tm.tmAveCharWidth * (str[9].GetLength() + 2);

   // Display strings 21-41
   for (i = 21; i < 42; i++)
   {
      pDC->TextOut(nLeft, nCurLineHeight, str);
      nCurLineHeight += tm.tmHeight;
   }
   if (pfntOld)
      pDC->SelectObject(pfntOld);
}

The LOGFONT Structure

If you need more information about a font or want to create a custom font, you have to understand logical fonts, which are defined with the Windows LOGFONT structure (see Listing 4.10). A logical font is an abstract font description that defines the font’s characteristics (bold, italic, size, and so on). A logical font must be selected into a device context before it can be used. After being selected into a device context, the logical font becomes a physical font.

Listing 4.10 The LOGFONT Structure Defines a Logical Font


typedef struct tagLOGFONT
{
   LONG lfHeight;
   LONG lfWidth;
   LONG lfEscapement;
   LONG lfOrientation;
   LONG lfWeight;
   BYTE lfItalic;
   BYTE lfUnderline;
   BYTE lfStrikeOut;
   BYTE lfCharSet;
   BYTE lfOutPrecision;
   BYTE lfClipPrecision;
   BYTE lfQuality;
   BYTE lfPitchAndFamily;
   TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

The members of this structure are described in Table 4.18.

Table 4.18 The Members of the LOGFONT Structure

Member Description

lfHeight The logical height of a character cell or character.
lfWidth The average logical width of characters in the font.
lfEscapement The angle, in tenths of degrees, between the escapement vector (parallel to the base line of a row of text) and the x-axis of the device.
lfOrientation The angle, in tenths of degrees, between each character’s base line and the x-axis of the device.
lfWeight The weight of the font (ranging from 0 to 1000).
lfItalic An italic font if TRUE.
lfUnderline An underlined font if TRUE.
lfStrikeOut A strikeout font if TRUE.
lfCharSet The font’s character set.
lfOutPrecision The output precision, which defines how closely the output must match a requested font’s height, width, character orientation, escapement, pitch, and font type.
lfClipPrecision The clipping precision, defining how to clip characters that lie on clipping boundaries.
lfQuality The output quality, defining how well the GDI must match logical-font attributes to physical font attributes.
lfPitchAndFamily The pitch and family of a font.
lfFaceName A string, limited to 32 characters, that denotes the typeface name of the font.

The LOGFONT structure is used when creating a logical font for use in a device context; the following section explains how.

Font Creation

MFC wraps logical fonts and methods into the CFont class. To create a logical font for use in your applications, you can use any of the initialization methods provided by the CFont class. Table 4.19 lists the creation methods class CFont provides.

Table 4.19 Class CFont Creation Methods

Method Description

CreateFontIndirect() Initializes a CFont object as defined by a LOGFONT structure.
CreateFont() Initializes a CFont object with the specified characteristics.
CreatePointFont() Initializes a CFont object with the specified height and typeface.
CreatePointFontIndirect() Initializes a CFont object as defined by a LOGFONT structure. Font height is measured in tenths of a point instead of logical units.



As you do with most MFC objects, you use two-step construction when creating a CFont object. The VECTEXT1 program’s CMainWnd::DisplayLogFont() method in Listing 4.11 shows how to create a logical font. This method also uses text metrics and logical font members to display the font in various locations and sizes. Calling the method like this produces the image in Figure 4.17:

DisplayLogFont(pDC, “Arial”);


Figure 4.17  The result of using CMainWnd::DisplayLogFont() to display various sizes of text.

Listing 4.11 Creating a Logical Font and Displaying 16 Sizes from 2 Points to 32 Points


///////////////////////////////////////////////////////////////////
// CMainWnd::DisplayLogFont()

void CMainWnd::DisplayLogFont(CClientDC* pDC, CString sFont)
{
   // Draw various sizes of fonts
   UINT uSize      = 2;   // Starting point size
   int  cyDrawHere = 30;  // Start vertical drawing text here
   CFont* pfntOld  = 0;

   // 16 lines of text, font sizes from 2 to 32 points
   for (int i = 0; i < 16; i++)
   {
      // Create a new font and init a LOGFONT structure
      CFont      fnt;
      LOGFONT    lf;
      TEXTMETRIC tm;

      memset(&lf, 0, sizeof(LOGFONT));

      // Set initial font typeface name and font size
      lstrcpy(lf.lfFaceName, sFont);

      int cyPixels = pDC->GetDeviceCaps(LOGPIXELSY);
      lf.lfHeight  = -MulDiv(uSize, cyPixels, 72);

      // Create the new font
      fnt.CreateFontIndirect(&lf);

      // Get the text metrics
      pDC->GetTextMetrics(&tm);
      cyDrawHere += abs(lf.lfHeight) + tm.tmExternalLeading;

      // Make the new font current in the DC
      pfntOld = pDC->SelectObject(&fnt);

      // Draw a line of text
      CString str;
      str.Format(“Font name: %s, size: %d points”,
                 lf.lfFaceName, uSize);

      pDC->TextOut(5, cyDrawHere, str);

      uSize += 2;
   }
   // Restore the previous font to the DC
   pDC->SelectObject(pfntOld);
}

The CDC::TextOut() method used in Listing 4.11 is only one of class CDC’s text output methods—text can be drawn in several ways.

Drawing Text

Text is drawn using a device context’s selected font, text color, and background color. You can set the text color by calling the CDC::SetTextColor() method; you can set the background color by calling the CDC::SetBkColor() and CDC::SetBkMode() methods.

To change the text in a DC to blue and make the text background transparent (so that what’s behind the text shows through), simply use this code (assuming that pDC is a pointer to CDC-derived object):

#define crBlue RGB(0, 0, 255)    // macro for naming a color

pDC->SetTextColor(crBlue);
pDC->SetBkMode(TRANSPARENT);

MFC provides several text output methods in class CDC. The most often used of these are DrawText(), TextOut(), and TabbedTextOut().

The DrawText() Method

The DrawText() method formats text within a given rectangle using many advanced formatting features, including expanding tabs into spaces; justifying text to the left, right, or center of the rectangle; and automatically breaking text into lines to fit within the rectangle. When using the CDC::DrawText() method, you usually use this overloaded prototype:

int DrawText(const CString& str, LPRECT lpRect, UINT nFormat);

The str parameter is a CString object that contains the text to draw; the lpRect parameter points to a RECT or CRect object that forms the bounding box in which the text is to be formatted; the nFormat parameter specifies how the text is formatted.

The TextOut() Method

The TextOut() method draws a text string at a specified location using the currently selected font and colors. The overloaded prototype you most often use is shown here:

BOOL TextOut(int x, int y, const CString& str);

In this syntax, x and y specify the logical coordinate where the text begins drawing, and str is a CString object that contains the text to draw.

The TabbedTextOut() Method

The TabbedTextOut() method draws a text string at the specified location and expands tabs to values specified in an array of tab-stop positions. The overloaded prototype you most often use is as follows:

CSize TabbedTextOut(int x, int y, const CString& str,
   int nTabPositions, LPINT lpnTabStopPositions,
   int nTabOrigin );

The parameters for this method are listed in Table 4.20.

Table 4.20 The Parameters of the CDC::TabbedTextOut() Method

Parameter Meaning

x, y The logical x and y coordinates of the starting point of the string.
str A CString object that contains the specified characters.
nTabPositions The number of values in the array of tab-stop positions.
lpnTabStopPositions A pointer to an array containing the tab-stop positions in logical units.
nTabOrigin Specifies the logical x coordinate of the tab expansion starting position.

Sample Program: Vector Graphics and Text Methods (VECTEXT1.EXE)

The VECTEXT1 program on the companion CD-ROM provides examples of many CDC graphics and text methods. The program creates a frame window with a non-sizable, dialog-style window border that handles mouse clicks (left and right) in its client area. The functionality for the program is mostly in the frame window class (CMainWnd). Many protected methods are provided as examples of many common graphics needs. These methods are listed and described in Table 4.21.


Note:  

The VECTEXT1 program is much too long to list in this book, but the full source code is on the CD-ROM.


Table 4.21 The Graphics and Helper Methods Found in the VECTEXT1 Program’s CMainWnd Class

Method Description

DoBezier() Displays three Bézier curves defined by ten control points.
DoEllipses() Displays six ellipses with various hatch brush styles.
DoLines() Displays randomly generated lines.
DoMetafile() Creates an enhanced metafile, records lines with random colors and thicknesses at random locations, and plays back the finished metafile on the CMainWnd client area.
DoPixels() Displays a random number of pixels with random locations and colors.
DoPoints() Displays 4×4-pixel-bounded ellipses with random locations and colors (just like DoPixels(), but with small 4×4 ellipses).
DoPolygon() Displays a filled polygon using the CMainWnd class array of points: m_apt[10].
DoPolyLine() Displays a polyline and its vertices using the CMainWnd class array of points: m_apt[10].
DoRects() Displays six rectangles with various hatch brush styles.
DoRegions() Displays two overlapping regions, one an elliptical region, the other a rectangular region.
DoRegionsUnion() Displays the result of the union of two overlapping regions, one an elliptical region, the other a rectangular region.
DoRegionsDifference() Displays the result of the difference of two overlapping regions, one an elliptical region, the other a rectangular region.
DoRegionsIntersect() Displays the result of the intersection of two overlapping regions, one an elliptical region, the other a rectangular region.
DoRoundRects() Displays six rounded rectangles filled with randomly colored solid brushes.
DoTextArial() Specifies, creates, and displays the standard Arial TrueType font in sizes from 2 points to 32 points.
DoTextRoman() Specifies, creates, and displays the standard Times New Roman TrueType font in sizes from 2 points to 32 points.
DoTextSystem() Displays complete TEXTMETRIC information about the standard system font.



The VECTEXT1 program uses the concept of graphics pages that change with each mouse click in the client area. The current “page” is determined by the CMainWnd member variable m_CurPage; each click triggers a call to one of the methods listed in Table 4.21 (within the CMainWnd::ProcessMouseClick() method), changing the “page.” When all the “pages” have been displayed, they start over from the beginning. The source code for this program should be enough to get you well on your way to mastering GDI graphics, MFC-style!

Raster Graphics

Vector graphics use mathematical formulas to describe images on a device context. Raster graphics differ from vector graphics in that they are based on information displayed in an array of pixels. Vector graphics are size-independent; they can be stretched to virtually any size without losing clarity or becoming distorted. Raster graphics, on the other hand, are constrained by the pixels that represent them. Resizing raster images most often results in distortion of the image, with a jagged, pixelized, staircase effect.

Named Raster Operations (ROPs)

Windows supports 256 ternary raster operations for use with the GDI bit block transfer operations (BitBlt() and related functions). These raster operations (ROPs) provide logical operations that combine the source, destination, and current brush pattern. Although there are 256 of these operations, in practice only a small number of them are used; only 15 are important enough to have earned common names for themselves. These 15 named ternary ROPs are described in Table 4.22.

Table 4.22 The 15 Named Ternary Raster Operations (ROPs) Used by GDI Bit Block Transfer Functions

ROP Description

BLACKNESS Creates black output in the destination bitmap.
DSTINVERT Inverts the destination bitmap.
MERGECOPY Combines a pattern bitmap with a source bitmap using the Boolean AND operator.
MERGEPAINT Inverts the source bitmap and combines it with the destination bitmap using the Boolean OR operator.
NOTSRCCOPY Inverts the source bitmap and copies it to the destination.
NOTSRCERASE Combines the destination and source bitmaps using the Boolean OR operator and then inverts the result.
PATCOPY Copies a pattern to the destination bitmap.
PATINVERT Combines the destination bitmap with a pattern using the Boolean XOR operator.
PATPAINT Inverts the source bitmap, combines it with a pattern using the Boolean OR operator, and combines the result with the destination bitmap using the Boolean OR operator.
SRCAND Combines the destination and source bitmaps using the Boolean AND operator.
SRCCOPY Copies the source bitmap to the destination bitmap.
SRCERASE Combines the inverted destination bitmap with the source bitmap using the Boolean AND operator.
SRCINVERT Combines the destination and source bitmaps using the Boolean XOR operator.
SRCPAINT Combines the destination and source bitmaps using the Boolean OR operator.
WHITENESS Creates white output in the destination bitmap.

Raster operation codes define how GDI combines the bits from a source bitmap with the bits in a destination bitmap. The ROP codes used for bit block transfer operations are called ternary ROPs because they use three operands:

  A source bitmap
  A brush
  A destination bitmap

Bitmaps

A bitmap is an array of bits that form an image of some kind. All raster devices, including video displays, use bitmaps to display images. The video display uses pixels to show the bitmap bits onscreen. Bitmaps come in various bit depths (or color depths) and flavors. The two types of bitmaps used by Windows are device-dependent bitmaps (DDBs) and device-independent bitmaps (DIBs). Both of these bitmap types can have different color depths.

The color depth of an image is directly related to the number of bits used to store the color data in a bitmapped image. A 1-bit image is called monochrome and can display two colors (black and white by default). A 4-bit image can display 16 colors (this is the standard for VGA video adapters). An 8-bit image can display up to 256 colors (the minimum for super VGA video adapters). A 16-bit image (also called a hicolor image) doesn’t need a palette to describe its colors; it can display 32,768 colors. A 24-bit image (also called a true color image) doesn’t use palettes because it can display the full spectrum of 16.8 million colors visible to the human eye.


Note:  

Bitmaps with color-depths ranging from 1 bit to 8 bits use palette information to specify the color table of the bitmapped image. Images with higher bit depths don’t need palettes.


Device-Dependent Bitmaps

Bitmaps that rely on the hardware palette are called device-dependent bitmaps (DDBs). DDBs were the only bitmap format available to Windows programmers before the release of Windows 3.0. DDBs are currently supported mainly for compatibility with older applications written for those early Windows versions. A developer writing a new application, or porting an application written for a previous version of Windows to the Win32 platform, should use DIBs. A DDB is defined by a Windows BITMAP structure and comes in two types:

  Discardable bitmaps—Windows can discard these from memory if the bitmap isn’t selected into a device context and system memory is running low.
  Nondiscardable bitmaps—Windows must retain these bitmaps in memory.

The BITMAP Structure

A DDB is defined by a BITMAP structure that holds the data for a bitmap. The BITMAP structure is defined as follows:

typedef struct tagBITMAP
{
   LONG   bmType;
   LONG   bmWidth;
   LONG   bmHeight;
   LONG   bmWidthBytes;
   WORD   bmPlanes;
   WORD   bmBitsPixel;
   LPVOID bmBits;
}
BITMAP;



The data members of this structure are described in Table 4.23.

Table 4.23 The Data Members of the BITMAP Structure

Member Description

bmType Specifies the bitmap type. Always set this member to zero.
bmWidth The width, in pixels, of the bitmap.
bmHeight The height, in pixels, of the bitmap.
bmWidthBytes The number of bytes per scan line. Because Windows expects the bit values of a bitmap to form a word-aligned array, this value must be divisible by 2.
bmPlanes The number of color planes in the bitmap.
bmBitsPixel The number of bits needed to describe the color of a pixel.
bmBits A pointer to the array of character values that make up the image data.

Device-Independent Bitmaps (DIBs)

Windows 3.0 introduced the device-independent bitmap (DIB), which was designed to solve some of the device dependency inherent in DDBs. DIBs are much more useful than DDBs for storing bitmap data in disk files because they contain more useful information about the image. Here are the main features of a DIB:

  Contains information about the color format of the device on which the DIB image was created.
  Contains information about the resolution of the device on which the DIB image was created.
  Contains information about the palette for the device on which the image was created.
  Contains an array of bits used to map the red, green, blue (RGB) color components of the palette to pixels in the DIB image.
  Contains a data-compression identifier that indicates which data compression scheme (if any) is used to compact the size of the file on disk.

The CBitmap Class

The CBitmap class encapsulates GDI bitmaps into a convenient MFC object wrapper that makes it easier to work with bitmaps in MFC than it is in the SDK. Figure 4.18 shows the location of CBitmap in the MFC class hierarchy.


Figure 4.18  The location of the CBitmap class in the MFC class hierarchy.

CBitmap Class Methods

The CBitmap class provides several class methods that wrap GDI bitmap-related functions. Table 4.24 describes these CBitmap methods.

Table 4.24 CBitmap Class Methods

Method Description

CreateBitmap() Creates a device-dependent memory bitmap with the specified width, height, and bit pattern.
CreateBitmapIndirect() Creates a bitmap with the width, height, and bit pattern defined in a BITMAP structure.
CreateCompatibleBitmap() Creates a bitmap compatible with a specified device context.
CreateDiscardableBitmap() Creates a discardable bitmap compatible with a specified device context.
FromHandle() Gets a pointer to a CBitmap object from a specified Windows bitmap handle (HBITMAP).
GetBitmap() Gets a pointer to a specified CBitmap object.
GetBitmapBits() Copies a bitmap’s bits into a specified buffer.
GetBitmapDimension() Gets the width and height of a bitmap that have been set previously by the SetBitmapDimension() method.
LoadBitmap() Loads a bitmap from resource data and attaches it to a CBitmap object.
LoadMappedBitmap() Loads a bitmap from resource data and maps colors to current system colors.
LoadOEMBitmap() Loads a predefined Windows bitmap and attaches it to a CBitmap object.
SetBitmapBits() Sets the bits of a bitmap to the specified bit values.
SetBitmapDimension() Designates a width and height to a bitmap using 0.1-millimeter units.

Class CBitmap also provides the operator HBITMAP, which returns the underlying Windows handle attached to the CBitmap object.

Transferring and Contorting Bitmaps

To transfer bitmap data from one place to another—that is, from one DC to another—you must transfer blocks of bits from one DC to another. This activity is commonly referred to as a bit block transfer (BBT), or BitBlt (pronounced bit-blit). When transferring bit blocks, you must use a memory DC as a buffer—you can’t “blit” directly to the display.

Transferring Bits with BitBlt()

The CDC class provides methods to blast bits from one DC to another; the most useful of these is CDC::BitBlt(). The prototype for this method is complex, taking eight parameters as shown here:

BOOL BitBlt(
   int x, int y,             // upper left corner of destination DC
   int nWidth, int nHeight,  // width and height of destination DC
   CDC* pSrcDC,              // the source DC
   int xSrc, int ySrc,       // upper left corner of source DC
   DWORD dwRop);             // the ternary raster operation code



The parameters for this method are described following:

  x and y—These values specify the logical coordinates of the upper-left corner of the destination rectangle.
  nWidth and nHeight—These values specify the logical height of the destination rectangle and source bitmap.
  pSrcDC—Pointer to a CDC object that identifies the device context from which the bitmap will be copied. It must be NULL if dwRop specifies a raster operation that does not include a source.
  xSrc and ySrc—These values specify the logical coordinates of the upper-left corner of the source bitmap.
  dwRop—This value specifies the ternary raster operation to be performed (refer to Table 4.22, earlier in this chapter).


Note:  

Not all device contexts support bit block transfers. You can test to see whether a DC conforms by calling the CDC::GetDeviceCaps() method, specifying the RASTERCAPS index as the sole parameter, and checking for the presence of the RC_BITBLT bit in the return value. Consider this example:

if ((dc.GetDeviceCaps(RASTERCAPS) & RC_BITBLT) == 0)
{
   // device doesn’t support BitBlt
   ;
}
else
{
   // device does support BitBlt
   ;
}

For more information about CDC::GetDeviceCaps() and the index values and bits used, see the MFC documentation for the Win32 API function of the same name.


Stretching Bits to Fit with StretchBlt()

The CDC class provides a method similar to BitBlt(), but one that stretches or compresses bits to fit a specified destination rectangle: CDC::StretchBlt(). The prototype for this method is even more complex than the one for BitBlt()StretchBlt() takes ten parameters as shown following:

BOOL StretchBlt(
   int x, int y,             // upper left corner of destination DC
   int nWidth, int nHeight,  // width and height of destination DC
   CDC* pSrcDC,              // the source DC
   int xSrc, int ySrc,       // upper left corner of source DC
   int nSrcWidth,            // width of the source bitmap
   int nSrcHeight,           // height of the source bitmap
   DWORD dwRop);             // the ternary raster operation code

The parameters for this method are the same as those used for BitBlt() with the exception of the two additional nSrcWidth and nSrcHeight values. These two values define the width and height of the source bitmap.


Note:  

To determine how to stretch or compress the bitmap, StretchBlt() uses the current stretching mode of the destination device context. This mode is set using the CDC::SetStretchBltMode() method.

Again, not all device contexts support StretchBlt() bit block transfers. You can test to see whether a DC conforms by calling the CDC::GetDeviceCaps() method, specifying the RASTERCAPS index as the sole parameter. As the final step, check the return value for the presence of the RC_STRETCHBLT flag. Consider this example:

if ((dc.GetDeviceCaps(RASTERCAPS) & RC_STRETCHBLT) == 0)
{
   // device doesn’t support StretchBlt
   ;
}
else
{
   // device does support StretchBlt
   ;
}

Bitmap Resources

Windows applications make use of several different kinds of resources. Icons, menus, and bitmaps are some examples of resources. Resources are clusters of binary data tacked on to the end of an application’s executable file during the linking process. When Windows fires up a program, it loads into memory only what it needs at that time. Resources typically stay on disk (floppy, hard, or compact) until Windows calls for them.

Most resources are discardable read-only data Windows can load or unload from memory at any time as current system memory requirements change. Even Windows itself uses resources to display its message boxes, file copy animations, toolbar bitmaps, and more. Next you’ll see how to create and use embedded bitmap resources in MFC programs, retrieving them on-the-fly at runtime with the relevant MFC methods.

Tacking Resources onto an Executable File

So how do you go about putting resources into a Windows executable file? After creating a resource, you must compile it, using a resource compiler, into a special resource file (a binary file with a .RES file extension). The resource file is then linked into your compiled executable to attach the resource data to your program. To tell the resource compiler what to include in the resource file, you must create a resource script: a text file with an .RC file extension that describes the resource. If external files are used (as they are with image resources), these files are named in the script.

After you create a bitmap file for the image resource, you must add a statement to your application’s resource script to identify it. For resources that reference external files, the syntax generally looks like this:

ImageName   IMAGETYPE   DISCARDABLE   FileName

For example, a resource statement for an icon with the resource identifier IDR_BMP (some integer value) and stored on disk in a bitmap file named MYIMAGE.BMP would look like this:

IDR_BMP   BITMAP   DISCARDABLE   “myimage.bmp”

Visual C++ does all of this for you automatically when you insert a bitmap resource using the Insert, Resource menu commands.

Getting Image Resources out of an Executable File

After a resource is embedded into an executable, the real trick is to successfully get it back out and use it. To retrieve resource data for use in your application at runtime, you can use a variety of functions provided by MFC and the Win32 API. The functions you use depend on the type of resource data. For image resources (icons, cursors, and bitmaps), you typically use the LoadImage() Win32 API function. The LoadImage() function prototype is as follows:

HANDLE LoadImage(
   HINSTANCE  hinst,      // instance handle containing the image
   LPCTSTR    lpszName,   // name or identifier of image
   UINT       uType,      // type of image
   int        cxDesired,  // desired width
   int        cyDesired,  // desired height
   UINT       fuLoad      // load flags
);

This function is used in all the sample programs in this chapter to load image resources. As you can see, the function returns a generic handle to your icon, cursor, or bitmap resource. Most of the parameters should look familiar, but some of them need a little explanation:

  The utype parameter can be any of three macros that specify the type of image to load: IMAGE_BITMAP loads a bitmap, IMAGE_CURSOR loads a cursor, and IMAGE_ICON loads an icon.
  The fuLoad parameter is any combination of the flags in Table 4.25.



Table 4.25 The LoadImage() Load Flags

Value Meaning

LR_DEFAULTCOLOR A default flag that just means “not LR_MONOCHROME”.
LR_CREATEDIBSECTION If the uType parameter specifies IMAGE_BITMAP, the function returns a DIB section bitmap instead of a DC-compatible bitmap (the default). This flag is useful for loading a bitmap without mapping its colors to a display device and gives you access to its palette information.
LR_DEFAULTSIZE Uses the width or height specified by the system metric values for cursors and icons if cxDesired or cyDesired are zero. If this flag isn’t specified and cxDesired and cyDesired are zero, the resource is loaded at its actual size. If the resource contains multiple images, the size of the first image is used.
LR_LOADFROMFILE Loads an image from the file specified by the lpszName parameter. If this flag isn’t specified, lpszName is the name of the resource.
LR_LOADMAP3DCOLORS Searches the image’s color table and replaces the various shades of gray with a corresponding Windows or Windows NT 4.0 3D system color.
LR_LOADTRANSPARENT Causes all pixels with the same color as the first pixel in the image to become the default window color (COLOR_WINDOW).
LR_MONOCHROME Loads an image in black and white.
LR_SHARED Shares the image handle if the image is loaded multiple times. If LR_SHARED is not set, each call to LoadImage() for the same resource loads the image again and returns a different handle each time.

Almost all the bitmaps you see attached to various Windows dialog boxes, animations, and image lists are bitmap resources. In the BITMAP1 program, the bitmap resources are loaded using the more convenient CBitmap::LoadBitmap() method.

Sample Program: Exploring Bitmap Resources (BITMAP1)

Now let’s look at a program that performs the remarkable feat of lighting up a bitmapped icon area when the mouse passes over it. The BITMAP1 program makes use of five bitmap resources: one ray-traced image for the background bitmap, two for the “unlit” versions of the icon bitmaps, and two for the “lit” versions of the icon bitmaps (see Figure 4.19). The BITMAP1 program is shown in Figure 4.20 at runtime with the BTN1LIT.BMP displayed.


Figure 4.19  The BITMAP1 program uses these four bitmap resources to simulate light-up bitmap areas.


Figure 4.20  The BITMAP1 program at runtime with the BTN1LIT.BMP displayed as the cursor passes over it.

The five bitmaps are compiled into a resource file (BITMAP1.RES) by telling the resource compiler about them. This is done in the resource script (BITMAP1.RC) for the program. The bitmap resource IDs are given in the header file RESOURCE.H, which is used by both the resource script (BITMAP1.RC) and the program’s sole source file (BITMAP1.CPP). The resulting resource file is linked into the BITMAP1 executable to provide access to the five bitmaps.

Examining the BITMAP1 Program

Let’s take a look at the important sections of code that perform the magic for the program. Listing 4.12 shows the CMainWnd class declaration for the BITMAP1 program. All the important goings-on within the program happen here.

Listing 4.12 The Declaration of Class CMainWnd in the BITMAP1 Program


///////////////////////////////////////////////////////////////////
// Class CMainWnd - derived from MFC’s CFrameWnd

class CMainWnd : public CFrameWnd
{
protected:
   CBitmap   m_bmpBack;     // resource IDR_BMPBACKGROUND
   CBitmap   m_bmpBtn1;     // resource IDR_BMPBTN1
   CBitmap   m_bmpBtn1Lit;  // resource IDR_BMPBTN1LIT
   CBitmap   m_bmpBtn2;     // resource IDR_BMPBTN2
   CBitmap   m_bmpBtn2Lit;  // resource IDR_BMPBTN2LIT

   CRect m_rcTop;     // rect for drawing
   CRect m_rcBtm;     // rect for drawing
   CRect m_rcClient;  // rect for drawing

   void BlitBitmap(CRect& rc, CBitmap& bm);
   void ShowBitmaps();

public:
   // Helper method called by MFC
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

   void PositionRects();

   // Message handler
   afx_msg BOOL OnEraseBkgnd(CDC* pDC);
   afx_msg void OnMouseMove(UINT nFlags, CPoint point);

   DECLARE_MESSAGE_MAP();
};

The message map for CMainWnd is as follows:

BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
   ON_WM_ERASEBKGND()
   ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

As you can see from the message map macros and the class declaration, the CMainWnd::OnEraseBkGnd() and CMainWnd::OnMouseMove() methods have some special importance.

In the CMainWnd::PreCreateWindow() method, the bitmap resources are loaded with several calls to CBitmap::LoadBitmap(), one for each bitmap resource:

// Get the bitmaps from the resource data
//
m_bmpBack.LoadBitmap(IDR_BMPBACKGROUND);
m_bmpBtn1.LoadBitmap(IDR_BMPBTN1);
m_bmpBtn1Lit.LoadBitmap(IDR_BMPBTN1LIT);
m_bmpBtn2.LoadBitmap(IDR_BMPBTN2);
m_bmpBtn2Lit.LoadBitmap(IDR_BMPBTN2LIT);



Now that the bitmap handles are safely stored in the member variables, the CMainWnd::OnEraseBkgnd() method makes use of them to draw the bitmaps on the client area of the window with the CMainWnd::BlitBitmap() method:

PositionRects();

// draw the background bitmap
BlitBitmap(m_rcClient, m_bmpBack);

// “Blit” the unlit versions to the screen
BlitBitmap(m_rcTop, m_bmpBtn1);
BlitBitmap(m_rcBtm, m_bmpBtn2);

The PositionRects() method does just that—positions the two rectangles in which the four small bitmaps are drawn:

m_rcTop.left  = 523; m_rcTop.top    = 70;
m_rcTop.right = 598; m_rcTop.bottom = 145;

m_rcBtm.left  = 523; m_rcBtm.top    = 168;
m_rcBtm.right = 598; m_rcBtm.bottom = 243;

The CMainWnd::BlitBitmap() method is the most important part of the program. It performs the actual work of drawing the bitmaps on the client area and is worth a closer look. The entire method is shown in Listing 4.13.

Listing 4.13 The BlitBitmap() Method Handles the Bitmap Drawing Chores for the BITMAP1 Program


///////////////////////////////////////////////////////////////////
// CMainWnd::BlitBitmap()

void CMainWnd::BlitBitmap(CRect& rc, CBitmap& bm)
{
   CClientDC dc(this);
   CDC       dcMem;
   HBITMAP   hbmpOld;

   // Get compatible memory DCs
   dcMem.CreateCompatibleDC(&dc);

   // Select bitmaps into the DCs
   hbmpOld = (HBITMAP)dcMem.SelectObject(bm);

   // “Blit” the bitmap to the screen
   //
   GetDC()->BitBlt(rc.left, rc.top, rc.right, rc.bottom,
                   &dcMem, 0, 0, SRCCOPY);

   // Clean up
   dcMem.SelectObject(hbmpOld);
}

The goal here is to put the bitmap data into a memory device context, and then do a bit block transfer (bit blit) to move the bitmap image to the display.


Note:  

The positions of the rectangles used for image “blitting” were determined during the creation of the large bitmap file used for the background bitmap resource.


A client device context (DC) is obtained for the frame window, and another device context is created for the memory DC (dcMem). The memory DC creates a memory device context compatible with the display surface by calling CDC::CreateCompatibleDC(). Then the handle to the bitmap you want to draw (the hbm method parameter) is passed to the Win32 API function SelectObject() along with dcMem. This copies the bitmap into the memory device context (which is compatible with the device), displacing the object currently in the DC. A handle to this object (which you store for later cleanup duties) is returned from SelectObject(). You then blast the bitmap bits to the display with a call to the CDC::BitBlt() method.

CDC::BitBlt() must know the destination rectangle for the image blit; you use the rc parameter sent to BlitBitmap() to define the destination drawing area. When the new bitmap is safely on the display, you select the original object back into the client DC, to leave things as you found them before the swap.

The final piece of the puzzle is to make the icon areas on the window appear to glow when the mouse passes over one of them. The mechanism for displaying the bitmaps has been explained, but the events leading up to the blitting frenzy haven’t. The controlling factor in all this is the position of the cursor on the display. The CMainWnd::OnMouseMove() method tracks the cursor position at all times. To determine whether to swap bitmaps or not, use the CPoint::PtInRect() method you’ve seen before. The following code snippet should make it all clear:

void CMainWnd::OnMouseMove(UINT nFlags, CPoint point)
{
   // check to see if the pointer is over a bitmap area
   //
   if (m_rcTop.PtInRect(point))
      BlitBitmap(m_rcTop, m_hbmpBtn1Lit); // “light” the top image

   else if (m_rcBtm.PtInRect(point))
      BlitBitmap(m_rcBtm, m_hbmpBtn2Lit); // “light” the bottom image

   else  // turn the lights off
   {
      // “Blit” the unlit versions to the screen
      BlitBitmap(m_rcTop, m_hbmpBtn1);
      BlitBitmap(m_rcBtm, m_hbmpBtn2);
   }
   // Call the inherited method
   CMainFrame::OnMouseMove(nFlags, point);
}

There you have it—how to light up bitmaps using embedded bitmap resources!

Summary

Windows provides a Graphical User Interface (GUI) to the underlying operating system and hardware, and provides hardware information to applications via device contexts: data structures that are logical representations of physical devices or virtual devices.

MFC provides device context classes for general devices (CDC), window client areas (CClientDC), total window area (CWindowDC), and for Windows metafiles (CMetaFileDC). MFC also provides graphic objects that are used to draw on these device contexts. These graphic objects are pens (CPen), brushes (CBrush), fonts (CFont), bitmaps (CBitmap), and regions (CRgn).

Vector graphics are mathematical descriptions of graphical imagery, and in this chapter you’ve looked at the vector graphics methods of the MFC device context classes. Vector images typically consist of lines, polygons, rectangles, regions, polygons, metafiles, fonts, and curves.

You’ve looked at the TEXTMETRIC structure that holds basic text information as it applies to the display. You’ve also seen that fonts are generally defined by a LOGFONT structure that holds information about font construction. Text output methods were presented, as was sample code from the sample program VECTEXT1 located on the companion CD-ROM. Working with the GDI graphics functions and CDC font and text methods does take some getting used to.

This chapter has also provided a look at the usage of bitmaps as resources stored as data within executable files, and along the way you’ve learned how to retrieve the image data using the Win32 API, and how display and manipulate them. All of this may seem complex at first, but with time, experimentation, and experience, you’ll be an old hand with graphics and text manipulation in no time!